<?php
/*
 * Created on Mar 7, 2009
 *
 * author: David Palmer <blinder.dave@gmail.com>
 * primary contributor: Craig Andrews
 * 
 * This class basically represents a very light-weight inversion-of-control
 * container so that we can easily map our actions to service classes, or
 * using Spring's "bean" paradigm. This is a first pass, and does not support
 * all of Spring's functionality
 */
class Container {
	private $beans;
	public $BEAN_CONTAINER = null;
	public $context;
	
	private $wqueue = array(); //wait/retry queue
	
	function __construct() {
		// lets invoke our beans so we have nice little object references
		//Tools::flushCache();
		$this->BEAN_CONTAINER = Tools::getCachedObject("bean-container");
		if (! $this->BEAN_CONTAINER) { 
			$context = new SimpleXmlElement(file_get_contents(CONTEXT_XML));
			$imported_contexts = array();
			// lets see if we need to import anything else before we really get going
			foreach ($context->import as $import) {
				if (file_exists($import['resource'])) { 
					$icontext = new SimpleXmlElement(file_get_contents($import['resource']));
					array_push($imported_contexts,$icontext);
				} else {
					throw new Exception ($import['resource'] . " was not found on import");
				}
			}
			$imported_xml="";
			foreach($imported_contexts as $ctx) {
				foreach($ctx->bean as $lb) {
					$xml = $lb->asXML();
					$imported_xml .= "\n" . $xml;
				}
			}	
			$parent = $context->xpath('bean');	
			$parent_xml="";
			foreach($parent as $bean) {
				$parent_xml .= $bean->asXML();
			}
			$full_xml = $parent_xml . "\n" . $imported_xml;
			
			$fcontext = simplexml_load_string("<beans>" . $full_xml . "</beans>");
			foreach ($fcontext->bean as $fbean) {
				$this->_processBean($fcontext,$fbean);
			}

			//TODO: uncomment this line - it's nice to not have caching for development
			Tools::setCachedObject("bean-container",$this->BEAN_CONTAINER);
		}
	}
	
	function getBean ($name) {
		if (array_key_exists($name,$this->BEAN_CONTAINER)) {
			$b = $this->BEAN_CONTAINER->$name;
			if ($b instanceof ProxyFactory) {
				// we need to do some executing before or after!!! we don't
				// care about returning anything else just the instance
				$interceptor = $b->getInterceptor();
				try { 
					$interceptor->before($b->getTarget(),$b->getMethod(),null);
				} catch (Exception $ex) {
					throw $ex;
				}
				return $b->getTarget();	
			}
			return $this->BEAN_CONTAINER->$name;
		} else {
			return null;
		}
	}
	
	private function _processBean($context,$bean) {
		$obj = "Initializing...";
		if($bean['id'] && $this->BEAN_CONTAINER->$bean['id']){
			return $this->BEAN_CONTAINER->$bean['id'];
		}else{
			// if we don't have an ID, lets make one
			if (!$bean['id']) {
				$bean['id'] = Tools::uuidSecure();
			}
			if($bean['id']){
				$this->BEAN_CONTAINER->$bean['id'] = &$obj;
			}

			$class = $bean["class"];
			if (!$class) {
				throw new Exception ("bean ID: " . $bean['id'] . " seems to not exist");
			}
			$class_path = str_replace(".", "/",$class) . ".php";
			$class_name = array_pop(explode("/",$class_path));
			$class_name = array_shift(explode(".",$class_name));

			/**
			 * TODO: This needs to be cleaned up and should probably use the classloader
			 * function in: libs/c_tools.php because if we ever add another classpath
			 * this would be quite broken
			 */
			if (! @include_once(CLASSPATH . "/" . $class_path)) {
				include_once($class_path);
			}
		
			if ($bean['constructor']) {
				$constructor = (string)$bean['constructor'];
				// @TODO: This isn't done yet. This is to handle constructor args
				if ($bean->constructor_arg) {
					foreach($bean->constructor_arg as $arg) {
					}
					
				} else { 
					$obj = new $constructor();	
				}
			} else { 
				$obj = new $class_name();
				
			}
			// now lets see if we have properties for this bean
			if (isset($bean->property)) {
				$obj = $this->_processProperties($context,$obj,$bean->property);
			}
		
			if(method_exists($obj,"afterPropertiesSet")){
				$obj->afterPropertiesSet();
			}
		
			return $obj;
		}
		
	}
	
	private function _processProperties ($context,$obj, $properties) {
		$ref_obj = new ReflectionObject($obj);
		foreach ($properties as $property) { 
			$prop_name = $property['name'];
			$method_name = "set" . ucfirst($prop_name);
			if (! $ref_obj->hasMethod($method_name)) {
				throw new Exception("object does not have setter: $method_name");
			}
			$method = $ref_obj->getMethod($method_name);
			$children = $property->children();
			if(count($children)!=1){
				throw new Exception("A Property tag must have exactly one element within it");
			}
			$value = $this->_materializeFromTag($context,$children[0]);
			if ($value != "__queued__") { 
				$method->invoke($obj,$value);
			}
		}
		return $obj;
	}
	
	private function _materializeFromTag($context,$tag){
		$tag_name = $tag->getName();
		if ($tag_name == "ref") {
			if ($tag['bean']) {
				$l_bean = $tag['bean'];

				$id_ref = $context->xpath("//bean[@id='".$l_bean."']");

				if (!empty($id_ref)) {
					$bean_ref = $context->$l_bean;

					$this->BEAN_CONTAINER->$l_bean = $this->_processBean($context,$id_ref[0]);
					$value = $this->BEAN_CONTAINER->$l_bean;
				} else {
					throw new Exception("Referenced bean $l_bean not defined in this context.");
				}
			} else if ($tag['local']) { 
				// this property has a local bean reference
				$l_bean = $tag['local'];
			
				$id_ref = $context->xpath("//bean[@id='".$l_bean."']");
				if (isset($id_ref)) {
					$bean_ref = $context->$l_bean;
					$this->BEAN_CONTAINER->$l_bean = $this->_processBean($context,$id_ref[0]);
					$value = $this->BEAN_CONTAINER->$l_bean;
				} else {
					throw new Exception("Referenced bean $l_bean not defined in this context.");
				}
			}
		} else if ($tag_name == "map") {
			$value = array();
			
			// @TODO: this needs to allow for beans to be values not just simple string values
			foreach ($tag->entry as $entry) {
				//$value[(string)$entry['key']] = $this->_materializeValue((string)$entry->value);
				$value[(string)$entry['key']] = $this->_materializeFromTag($context,$entry);
			}
		} else if ($tag_name == "list") {
			$value = array();
			$count = 0;
			foreach($tag->xpath("*") as $listItem) {
				$count++;
				$value[$count] = $this->_materializeFromTag($context,$listItem);
			}
		} else if ($tag_name == "bean") {
			$value = $this->_processBean($context,$tag);
		} else if ($tag_name == "value") {
			$value = $this->_materializeValue((string) $tag);
		}else{
			throw new Exception("Unknown tag: $tag_name");
		}
		return $value;
	}
	
	private function _materializeValue($value) {
		$placeholders = array();
		foreach ($this->BEAN_CONTAINER as $bean) {
			if ($bean instanceof PropertyPlaceholderConfigurer) {
				$placeholders = $bean->params;
			}
		}
		$pattern = '/\$\{(.*)\}/';
		if (preg_match($pattern,$value,$matches)) { 
			$m_count = count($matches);
			for ($i=1; $i<$m_count; $i++) {
				$key = $matches[$i];
				if ($placeholders[$key]) { 
					$value = preg_replace($pattern,$placeholders[$key],$value);
				}
			}
			return $value;
		} else {
			return $value;
		}
	}
}
?>